// Copyright(c) 2008 Tri Tech Information Systems Inc. 
// Distributed under the Boost Software License, Version 1.0.
//     (See accompanying file ../LICENSE_1_0.txt or copy at
//           http://www.boost.org/LICENSE_1_0.txt)
//     

#include <boost/python.hpp>
#include <pyxx/exception.h>
#include <pyxx/python_error.h>
#include <stdexcept>
#include <iostream>

using namespace boost::python;

class ErrorApiImpl : public ErrorApi
{
    public:
        virtual void raise(object type, object val, object tb, bool throw_cpp_error)
        {
            python_error pyerr(type, val, tb);

            if( throw_cpp_error && PyObject_HasAttrString(val.ptr(), "_thrower" ) )
            {
                try
                {
                    boost::python::object err_throw = val.attr("_thrower");
                    typedef void (*throw_func)(boost::python::object, const std::string&, object, object, object);

                    throw_func thrower = reinterpret_cast<throw_func>(PyCObject_AsVoidPtr(err_throw.ptr()));
                    thrower(val, pyerr.what(), type, val, tb);
                }
                catch(boost::python::error_already_set&)
                {
                    python_error::raise(false);
                }

            }        

            throw pyerr;

        }
    protected:
};

template<typename ExceptionType>
struct register_exception_to_python
{

    typedef register_exception_to_python<ExceptionType> register_type;



    register_exception_to_python(const char * name)
    {
        object e = exception_<ExceptionType, detail::not_specified>(name, true)
            .def(init<const std::string &>())
            .def("__str__", &ExceptionType::what)
            ;
        dict globals = dict(import("__builtin__").attr("__dict__"));
        dict context;
        object init_func = exec(
                "def init(self, *args, **kargs):\n"
                "    if len(args) == 1 and isinstance(args[0], self._cpp_error):\n"
                "        self.cpp_error = args[0]\n"
                "    else:\n"
                "        self.cpp_error = self._cpp_error(*args, **kargs)\n"
                "    Exception.__init__(self, str(self.cpp_error))\n",
                globals, context);
        e.attr("py_err_class").attr("__init__") = context["init"];

        register_exception_translator<ExceptionType>(&register_type::exception_to_python);


    }

    static void exception_to_python(const ExceptionType & err)
    {
        if( IPythonError * error = dynamic_cast<IPythonError*>(const_cast<ExceptionType*>(&err) ) )
        {
            error->restore();
        }
        else
        {
            object pyerr(ptr(&err)); // wrap the object in a python error
            pyerr = pyerr.attr("__class__")(pyerr);
            object err_klass = pyerr.attr("py_err_class");
            object python_exception = err_klass(pyerr);
            PyErr_SetObject( err_klass.ptr(), incref( python_exception.ptr() ) );
        }
    }

};

void restore_python_error(const IPythonError & err)
{
    // In this case, an error was thrown from python,
    // passed through c++ and back into python
    // we'll put the original error back
    const_cast<IPythonError*>(&err)->restore();
}

void WrapException()
{
    register_exception_to_python<std::runtime_error>("StdRuntimeError");
    register_exception_to_python<std::logic_error>("logic_error");
    register_exception_translator<IPythonError>(&restore_python_error);
//    register_exception_translator<python_error>(&restore_python_error);
//
    scope s;
    s.attr("_error_api") = object( handle<>(PyCObject_FromVoidPtr(new ErrorApiImpl, NULL)) );
 
}


